/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "gendata.hh"

#include "metadata.hh"
#include "dwarf.hh"
#include "source.hh"
#include "util.hh"

#include <stdio.h>
#include <errno.h>
#include <string.h>

#define INC_DATA         4096

// CLASS GENDATA
gendata::gendata(metadata *meta)
{
    // init user data
    this->meta=meta;
    meta->set_user_data(this);

    // doc
    if(!(doc=new string_index)) fprintf(stderr, "String index alloc\n");

    // data
    util::ctor_vector(data, n_data, max_data);
    i_data=0;

    // type remap
    n_type_remap=meta->get_dwarf()->analyze_get_n_base();
    type_remap=(int*)malloc(n_type_remap*sizeof(int));
    for(int i=0; i<n_type_remap; i++) type_remap[i]=-1;

    // checks
    if(!meta->get_out(0)) fprintf(stderr, "Output file error\n");
    if(sizeof(gendata_structs)!=GENDATA_LAST<<2)
	fprintf(stderr, "Internal: Bad output data structs, size of struct vector %d %d\n",
		sizeof(gendata_structs), GENDATA_LAST<<2);
    for(int i=0; i<GENDATA_LAST; i++)
    {
	if(gendata_structs[i][0]!=i)
	    fprintf(stderr, "Internal: Bad output data structs, initial value\n");
	if(gendata_structs[i][1]!=GENDATA_GENDATA_TYPE)
	    fprintf(stderr, "Internal: Bad output data structs, second value must be G_G_TYPE\n");
    }
}

gendata::~gendata()
{
    // correct GENDATA_GENDATA_VAR because of GENDATA_GENDATA_GOTO_NEXT items
    correct_var_fields_relativize_type();

    // write output
    write_to_file();
    dump();

    // free memory
    if(doc) delete doc;

    // data
    util::dtor_vector(data, n_data, max_data);

    // type remap
    if(type_remap) type_remap=(int*)realloc(type_remap, 0);
    n_type_remap=0;
}

void gendata::write_to_file(int i_out)
{
    // check
    if(n_data!=i_data) fprintf(stderr, "internal: two passes with different sizes %d!=%d\n", n_data, i_data);

    FILE *out;
    if((out=meta->get_out(i_out)))
    {
	if(fwrite(&n_data, sizeof(int), 1, out)!=1 ||
	   fwrite(data, sizeof(int), n_data, out)!=(unsigned)n_data)
	    fprintf(stderr, "Error writting data to file: %s\n", strerror(errno));
	int n_doc=doc->get_n_doc();
	if(fwrite(&n_doc, sizeof(int), 1, out)!=1 ||
	   fwrite(doc->get_doc(), sizeof(char), doc->get_n_doc(), out)!=(unsigned)doc->get_n_doc())
	    fprintf(stderr, "Error writting doc to file: %s\n", strerror(errno));
    }
}

void gendata::write(int value)
{
    if(!meta->get_repetition())
    {
	int i=util::inc_vector(data, n_data, max_data, INC_DATA);
	data[i]=value;
    }
    else i_data++;
}

void gendata::write_string(int i_str)
{
    if(i_str<0) write(-1);
    else
    {
	char *str=meta->get_doc()->string(i_str);
	write(doc->search_add_string(str));
    }
}

void gendata::write_comment()
{
    dwarf_base *b=get_base();
    char *comment;
    if(b->line>=0 && (comment=meta->get_source()->comment(b->i_source, b->line)))
	write(doc->search_add_string(comment));
    else
	write(-1);
}

void gendata::write_location()
{
    dwarf_base *b=get_base();
    if(b->bit.value_meaning!=DW_VALUE_LOCATION)
    {
	// this may be the case of members in an union
	if(b->type==DW_TAG_member && b->i_parent>=0 &&
	   meta->get_dwarf()->analyze_get_base(b->i_parent)->type==DW_TAG_union_type)
	{
	    write(0);
	}
	// variables are prefered to use linkage, so it's not an error if location is missing
	else if(b->type==DW_TAG_variable)
	{
	    write(-1);
	}
	else
	{
	    fprintf(stderr, "Base has no location\n");
	    write(-1);
	    meta->get_dwarf()->analyze_dump(NULL, meta->get_dwarf()->bp_index());
	}
    }
    else write(b->value.location);
}

void gendata::write_upper_bound()
{
    dwarf_base *b=get_base();
    if(b->bit.value_meaning!=DW_VALUE_UPPER_BOUND)
    {
	fprintf(stderr, "Base has no upper bound\n");
	write(-1);
    }
    else write(b->value.upper_bound);
}

void gendata::write_const_value()
{
    dwarf_base *b=get_base();
    if(b->bit.value_meaning2!=DW_VALUE2_CONST_VALUE)
    {
	fprintf(stderr, "Base has no const value\n");
	write(-1);
    }
    else write(b->value2.const_value);
}

void gendata::write_linkage()
{
    dwarf_base *b=get_base();
    if(b->bit.value_meaning!=DW_VALUE_LINKAGE)
    {
	fprintf(stderr, "Base has no linkage\n");
	write(-1);
    }
    else write_string(b->value.linkage);
}

void gendata::write_vtable_linkage()
{
    dwarf_base *b=get_base();
    if(b->bit.value_meaning!=DW_VALUE_VTABLE_LINK) write(-1); // no problem, just a non-virtual class
    else write_string(b->value.vtable_linkage);
}

void gendata::write_vtable()
{
    dwarf_base *b=get_base();
    if(!b->bit.virt) write(-1);
    else if(b->bit.value_meaning2!=DW_VALUE2_VTABLE_POS)
    {
	fprintf(stderr, "TODO: vtable error\n");
	write(-1);
    }
    else write(b->value2.vtable_pos);
}

void gendata::write_type()
{
    if(!meta->get_repetition()) write(-1);
    else
    {
	// repetition of the pass, search type
	dwarf_base *b=get_base();
	int n_indir=b->bit.pattern_match;
	if(n_indir<=0) fprintf(stderr, "internal: pattern_match <=0\n");
	else
	{
	    while(n_indir!=0x7f && --n_indir)
	    {
		if(b->i_subtype<0)
		{
		    fprintf(stderr, "internal: cannot make desired number of indirections (%d to go)\n", n_indir);
		    break;
		}
		b=&meta->get_dwarf()->analyze_get_bases()[b->i_subtype];
	    }

	    // write subtype data
	    if(b->i_subtype>=0 && n_indir!=0x7f)
	    {
		if(b->i_subtype<n_type_remap) data[i_data++]=type_remap[b->i_subtype];
		else fprintf(stderr, "internal: remaping in second repetition\n");
	    }
	    else i_data++;
	}
    }
}

void gendata::write_child_number()
{
    dwarf_base *b=get_base();
    write(b->n_child);
}

dwarf_base *gendata::get_base()
{
    return meta->get_dwarf()->bp();
}

int gendata::write_struct(gendata_type type)
{
    dwarf_base *b=get_base();
    int i=0, val;

    // early check of linkage
    int linkage=1;
    while((val=gendata_structs[type][++i])!=GENDATA_META_LAST)
	if(val==GENDATA_LINKAGE)
	{
	    if(b->bit.value_meaning!=DW_VALUE_LINKAGE || b->value.linkage==-1)
	    {
		// cases where no linkage is allowed
		if(b->type!=DW_TAG_variable)
		    fprintf(stderr, "error, no linkage for %s%s%s (omitting)\n",
			    b->i_namespace>=0?meta->get_doc()->string(b->i_namespace):"",
			    b->i_namespace>=0?"::":"",
			    meta->get_doc()->string(b->i_name));
		linkage=0;
	    }
	    break;
	}

    // type_remap
    if(!meta->get_repetition())
    {
	int i_base=meta->get_dwarf()->bp_index();
	if(i_base<n_type_remap) type_remap[i_base]=n_data;
	else fprintf(stderr, "internal: type remap out of bounds\n");
    }

    i=0;
    while((val=gendata_structs[type][++i])!=GENDATA_META_LAST)
    {
	switch(val)
	{
	case GENDATA_GENDATA_TYPE: write(type); break;
	case GENDATA_GENDATA_GOTO_NEXT:
	    if(!meta->get_dwarf()->analyze_move_next_in_level())
	    {
		fprintf(stderr, "error going to next base\n");
		return 0;
	    }
	    b=get_base();
	    break;
	case GENDATA_NAME: write_string(b->i_name); break;
	case GENDATA_DOC: write_comment(); break;
	case GENDATA_SIZE: write(b->size); break;
	case GENDATA_TYPE: write_type(); break;
	case GENDATA_LOCATION: write_location(); break;
	case GENDATA_GENDATA_VAR: write_child_number(); break;
	case GENDATA_LINKAGE: if(linkage) write_linkage(); else write(-1); break;
	case GENDATA_IS_CTOR: write(b->bit.ctor); break;
	case GENDATA_VTABLE: write_vtable(); break;
	case GENDATA_VTABLE_LINKAGE: write_vtable_linkage(); break;
	case GENDATA_ARTIFICIAL: write(b->bit.artificial); break;
	case GENDATA_GENDATA_RESERVE: write(0); break;
	case GENDATA_NAMESPACE: write_string(b->i_namespace); break;
	case GENDATA_UPPER_BOUND: write_upper_bound(); break;
	case GENDATA_CONST_VALUE: write_const_value(); break;
	case GENDATA_ATTR: write_string(b->i_attr); break;
	default:
	    fprintf(stderr, "Gendata struct field type not implemented: %d\n", val);
	    return 0;
	}
    }
    return 1;
}

int gendata::init_pass()
{
    i_data=0;
    return 1;
}

void gendata::dump(int i_out)
{
    FILE *out=meta->get_out(i_out);
    if(!out) out=stdout;
    gendata_dump(out, data, n_data, doc->get_doc());
}

void gendata::correct_var_fields_relativize_type_recur(int &i, int pos_var_value)
{
    int metadata_init=i;
    int type=data[i];
    if(type<0 || type>=GENDATA_LAST) {fprintf(stderr, "Badly generated data: %d %d\n", i, type); return;}
    int j=0, val;
    while((val=gendata_structs[type][++j])!=GENDATA_META_LAST)
    {
	if(val==GENDATA_GENDATA_GOTO_NEXT)
	{
	    if(pos_var_value>=0) data[pos_var_value]--;
	    continue;
	}
	else if(val==GENDATA_GENDATA_VAR)
	{
	    int pos=i;
	    val=data[i++];
	    for(int k=0; k<val; k++) correct_var_fields_relativize_type_recur(i, pos);
	    continue;
	}
	else if(val==GENDATA_TYPE)
	{
	    // relativize type: from actual metadata_init to type metadata init
	    int pos_type=data[i];
	    if(pos_type<0) data[i]=0; else data[i]=pos_type-metadata_init;
	}
	i++;
    }
}

void gendata::correct_var_fields_relativize_type()
{
    int i=0;
    while(i<n_data) correct_var_fields_relativize_type_recur(i, -1);
}

// INTERFACE FUNCTIONS

int gendata_init(metadata *meta)
{
    new gendata(meta);
    return 1;
}

int gendata_finish(metadata *meta)
{
    delete (gendata*)meta->get_user_data();
    meta->set_user_data(NULL);
    return 1;
}

int gendata_init_pass(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->init_pass();
}

int gendata_finish_pass(metadata *meta)
{
    return 1;
}

int gendata_next(metadata *meta)
{
    return 1;
}

int gendata_down(metadata *meta)
{
    return 1;
}

int gendata_up(metadata *meta)
{
    return 1;
}

int gendata_type_recur(metadata *meta)
{
    return 1;
}

int gendata_class(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_CLASS);
}

int gendata_inheritance(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_INHERITANCE);
}

int gendata_member(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_MEMBER);
}

int gendata_member_object(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_MEMBER_OBJECT);
}

int gendata_method(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_METHOD);
}

int gendata_parameter(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_PARAMETER);
}

int gendata_charp(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_CHARP);
}

int gendata_const_charp(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_CONST_CHARP);
}

int gendata_int(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_INT);
}

int gendata_uint(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_UNSIGNED_INT);
}

int gendata_short(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_SHORT);
}

int gendata_ushort(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_UNSIGNED_SHORT);
}

int gendata_float(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_FLOAT);
}

int gendata_double(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_DOUBLE);
}

int gendata_long_long(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_LONG_LONG);
}

int gendata_ulong_long(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_UNSIGNED_LONG_LONG);
}

int gendata_variable(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_VARIABLE);
}

int gendata_member_vector_n_max(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_MEMBER_VECTOR_N_MAX);
}

int gendata_member_std_vector_impl(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_MEMBER_STD_VECTOR_IMPL);
}

int gendata_object_pointer(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_OBJECT_POINTER);
}

int gendata_function_pointer(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_FUNCTION_POINTER);
}

int gendata_char(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_CHAR);
}

int gendata_uchar(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_UNSIGNED_CHAR);
}

int gendata_subroutine(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_SUBROUTINE);
}

int gendata_pointer(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_POINTER);
}

int gendata_reference_count(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_REFERENCE_COUNT);
}

int gendata_reference(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_REFERENCE);
}

int gendata_const(metadata *meta)
{
    return 1;
}

int gendata_void_const(metadata *meta)
{
    return 1;
}

int gendata_void_pointer(metadata *meta)
{
    fprintf(stderr, "Should never reach here: void_pointer\n");
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_VOID_POINTER);
}

int gendata_boolean(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_BOOL);
}

int gendata_enumeration_type(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_ENUM);
}

int gendata_enumerator(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_ENUM_VALUE);
}

int gendata_array_type(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_ARRAY_TYPE);
}

int gendata_subrange_type(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_SUBRANGE);
}

int gendata_unspecified_parameters(metadata *meta)
{
    return ((gendata*)meta->get_user_data())->write_struct(GENDATA_UNSPEC_PARAM);
}

int gendata_subroutine_type(metadata *meta)
{
    return 1;
}
